//   Copyright 2012,2013 Vaughn Vernon
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
package com.saasovation.common.domain.model;

import com.saasovation.common.notification.NotificationReader;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.saasovation.common.port.adapter.messaging.Exchanges.*;

public class EventTracking {
    private List<Class<? extends DomainEvent>> handledEvents = new ArrayList<>();
    private Map<String, String> handledNotifications = new HashMap<>();
    private final Object lock = new Object();

    public void expectedEvent(Class<? extends DomainEvent> aDomainEventType) {
        this.expectedEvent(aDomainEventType, 1);
    }

    public void expectedEvent(Class<? extends DomainEvent> aDomainEventType, int aTotal) {
        int count = 0;
        for (Class<? extends DomainEvent> type : this.handledEvents) {
            if (type == aDomainEventType) {
                ++count;
            }
        }
        if (count != aTotal) {
            throw new IllegalStateException("Expected " + aTotal + " "
                    + aDomainEventType.getSimpleName() + " events, but handled "
                    + this.handledEvents.size() + " events: " + this.handledEvents);
        }
    }

    public void expectedEvents(int anEventCount) {
        if (this.handledEvents.size() != anEventCount) {
            throw new IllegalStateException("Expected " + anEventCount +
                    " events, but handled " + this.handledEvents.size()
                    + " events: " + this.handledEvents);
        }
    }

    public void expectedNotification(Class<? extends DomainEvent> aNotificationType) {
        this.expectedNotification(aNotificationType, 1);
    }

    public void expectedNotification(Class<? extends DomainEvent> aNotificationType, int aTotal) {
        try {
            Thread.sleep(200L);
        } catch (InterruptedException e) {
            // ignore
        }
        int count = 0;
        String notificationTypeName = aNotificationType.getName();
        for (String type : this.handledNotifications.values()) {
            if (type.equals(notificationTypeName)) {
                // System.out.println("MATCHED: " + type);
                // System.out.println("WITH: " + notificationTypeName);
                ++count;
            }
        }
        if (count != aTotal) {
            throw new IllegalStateException("Expected " + aTotal + " "
                    + aNotificationType.getSimpleName()
                    + " notifications, but handled "
                    + this.handledNotifications.size() + " notifications: "
                    + this.handledNotifications.values());
        }
    }

    public void expectedNotifications(int anNotificationCount) {
        try {
            Thread.sleep(200L);
        } catch (InterruptedException e) {
            // ignore
        }
        if (this.handledNotifications.size() != anNotificationCount) {
            throw new IllegalStateException("Expected " + anNotificationCount
                    + " notifications, but handled "
                    + this.handledNotifications.size() + " notifications: "
                    + this.handledNotifications.values());
        }
    }

    public void setHandledEvents(List<Class<? extends DomainEvent>> handledEvents) {
        this.handledEvents = handledEvents;
    }

    public List<Class<? extends DomainEvent>> handledEvents() {
        return handledEvents;
    }

    public void setHandledNotifications(Map<String, String> handledNotifications) {
        this.handledNotifications = handledNotifications;
    }

    public Map<String, String> handledNotifications() {
        return handledNotifications;
    }

    @RabbitListener(bindings = {@QueueBinding(
            value = @Queue(),
            exchange = @Exchange(
                    value = IDENTITY_ACCESS_EXCHANGE_NAME,
                    type = ExchangeTypes.FANOUT,
                    declare = Exchange.FALSE),
            key = "")})
    public void identityEventCollector(Message message) {
        String aType = message.getMessageProperties().getType();
        byte[] body = message.getBody();
        String jsonMsg = new String(body, StandardCharsets.UTF_8);
        synchronized (lock) {
            NotificationReader notification = new NotificationReader(jsonMsg);
            String notificationId = notification.notificationIdAsString();
            this.handledNotifications.put(notificationId, aType);
        }
    }

    @RabbitListener(bindings = {@QueueBinding(
            value = @Queue(),
            exchange = @Exchange(
                    value = COLLABORATION_EXCHANGE_NAME,
                    type = ExchangeTypes.FANOUT,
                    declare = Exchange.FALSE),
            key = "")})
    public void collaborationEventCollector(Message message) {
        String aType = message.getMessageProperties().getType();
        byte[] body = message.getBody();
        String jsonMsg = new String(body, StandardCharsets.UTF_8);
        synchronized (lock) {
            NotificationReader notification = new NotificationReader(jsonMsg);
            String notificationId = notification.notificationIdAsString();
            this.handledNotifications.put(notificationId, aType);
        }
    }

    @RabbitListener(bindings = {@QueueBinding(
            value = @Queue(),
            exchange = @Exchange(value = AGILE_PM_EXCHANGE_NAME,
                    type = ExchangeTypes.FANOUT,
                    declare = Exchange.FALSE),
            key = "")})
    public void agilePmEventCollector(Message message) {
        String aType = message.getMessageProperties().getType();
        byte[] body = message.getBody();
        String jsonMsg = new String(body, StandardCharsets.UTF_8);
        synchronized (lock) {
            NotificationReader notification = new NotificationReader(jsonMsg);
            String notificationId = notification.notificationIdAsString();
            this.handledNotifications.put(notificationId, aType);
        }
    }
}
